package com.vivz.guess.what.util;

import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import android.text.TextUtils;

import com.vivz.guess.what.type.Position;
import com.vivz.guess.what.vo.Clue;
import com.vivz.guess.what.vo.Guess;

public final class GuessWhatUtil implements GuessWhatDictionary {

	/**
	 * Implies that a character in the guess-word is in the keyword and in the same position.
	 */
	public static final char CROSS = 'x';

	/**
	 * Implies that a character in the guess-word is in the keyword but in a different position.
	 */
	public static final char DOT = '.';

	public static final String EMPTY_STRING = "";

	/**
	 * Implies that none of the characters in the guess-word is in the keyword.
	 */
	public static final String WEIGHT_ZERO = "----";

	/**
	 * Calculate the number of letters from the guess which are in the keyword. Differentiate the ones that are in the
	 * right place (with an 'x') from those that are not (with a '.')
	 * 
	 * @param keyWord
	 * @param guessWord
	 * @return
	 */
	public static final String calculateWeight(String keyWord, String guessWord) {

		if (keyWord == null || guessWord == null || keyWord.length() != 4 || guessWord.length() != 4) {
			return WEIGHT_ZERO;
		}

		final StringBuffer weight = new StringBuffer();
		keyWord = keyWord.toUpperCase(Locale.US);
		guessWord = guessWord.trim().toUpperCase(Locale.US);

		for (int i = 0; i < 4; i++) {

			final char guessChar = guessWord.charAt(i);
			final int indexOfGuessCharInKeyword = keyWord.indexOf(guessChar);
			if (indexOfGuessCharInKeyword >= 0) {
				if (i == indexOfGuessCharInKeyword) {
					weight.append(CROSS);
				} else {
					weight.append(DOT);
				}
			}
		}

		if (weight.length() == 0) {
			return WEIGHT_ZERO;
		}
		return weight.toString();
	}

	/**
	 * @param guessWord
	 * @param keyword
	 * @return returns a {@link Clue} object based on guessWord and keyword
	 */
	public static final Clue calculateClue(final String guessWord, final String keyword) {
		final String weight = calculateWeight(keyword, guessWord);
		final int countX = count(weight, CROSS);
		final int count0 = count(weight, DOT);
		final Clue clue = new Clue(countX, count0);
		return clue;
	}

	/**
	 * @param guess1
	 * @param guess2
	 * @returns a map of all characters in guess1 (with their positions) that are not in guess2.
	 */
	public static final Map<Character, Position> getDifferenceAgainstGuess(final Guess guess1, final Guess guess2) {

		final Map<Character, Position> difference = new HashMap<Character, Position>();

		final String word1 = guess1.getWord();
		for (final Character c1 : word1.toCharArray()) {
			final String word2 = guess2.getWord();
			final String upperWord2 = word2;
			final String upperC1 = String.valueOf(c1);
			if (!upperWord2.contains(upperC1)) {
				difference.put(c1, Position.getInstance(1 + word1.indexOf(c1)));
			}
		}

		return difference;
	}

	/**
	 * @param g
	 * @param definiteOutsiders
	 * @return a map of all characters in g (with their positions) that are not in definiteOutsiders.
	 */

	public static final Map<Character, Position> getDifferenceAgainstSet(final Guess g, final Set<Character> definiteOutsiders) {
		final Map<Character, Position> map = new HashMap<Character, Position>();
		if (definiteOutsiders != null) {
			for (final Character c : g.getWord().toCharArray()) {
				if (!definiteOutsiders.contains(c)) {
					map.put(c, Position.getInstance(g.getWord().indexOf(c) + 1));
				}
			}
		}
		return map;
	}

	/**
	 * Tests if the passed text has only 4 characters - not more not less.
	 * 
	 * @param text
	 *            the string to test for length
	 * @param degree
	 *            the length of the desired trimmed text
	 * @return <code>true</code> if absolute value of text after trimming is 4; <code>false</code> otherwise.
	 */
	public static final boolean isNCharactersLong(final String text, final int degree) {
		return text != null && text.trim().length() == degree;
	}

	/**
	 * Tests if the passed value has only letters and no other characters.
	 * 
	 * @param text
	 * @return
	 */
	public static final boolean isComprisedOnlyOfLetters(String text) {

		text = text.trim();

		for (int i = 0; i < text.length(); i++) {
			if (!Character.isLetter(text.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	/**
	 * TODO: Replace with actual functionality to check against a valid comprehensive dictionary.
	 * 
	 * @param text
	 *            - the text to lookup
	 * @return
	 */
	public static final boolean isInDictionary(String text) {

		text = text.trim().toUpperCase(Locale.US);
		// final String vowelRegex = "^.*([AEIOU])+.*$";
		// return text.matches(vowelRegex);

		final List<String> allWords = Arrays.asList(KEYWORDS);
		return allWords.contains(text);
	}

	/**
	 * Employs a regex to tell whether the string has any repeating characters or not.
	 * 
	 * @param text
	 *            - the text to validate
	 * @return true if it matches the regex (?i:([A-Z])(?!\1)([A-Z])(?!\1|\2)([A-Z])(?!\1|\2|\3)([A-Z])) , false
	 *         otherwise
	 */
	public static final boolean isMadeOfUniqueCharacters(String text) {
		text = text.trim().toUpperCase(Locale.US);
		final String regex = "(" + "?i:" + "([A-Z])(?!\\1)" + "([A-Z])(?!\\1|\\2)" + "([A-Z])(?!\\1|\\2|\\3)"
				+ "([A-Z])" + ")";
		return text.matches(regex);
	}

	/**
	 * Employs a regex to tell whether the string has any repeating characters or not.
	 * 
	 * @param text
	 *            - the text to validate
	 * @return true if it matches the regex (?i:([A-Z])(?!\1)([A-Z])(?!\1|\2)([A-Z])(?!\1|\2|\3)([A-Z])) , false
	 *         otherwise
	 */
	public static final Character repeatedCharacters(final String text) {
		final Set<Character> chars = new HashSet<Character>();
		Character repeatedChar = null;
		for (final Character c : text.toCharArray()) {
			if (!chars.add(c)) {
				repeatedChar = c;
				break;
			}
		}
		return repeatedChar;
	}

	// public static void main(String[] args) {
	//
	// String text = "king";
	// URL url = null;
	// try {
	// url = new URL("http://www.merriam-webster.com/dictionary");
	// } catch (MalformedURLException e) {
	// e.printStackTrace();
	// }
	// System.out.println(linkify(text, url));
	// }
	//
	/**
	 * Validates word before guessing. Constraints - 1. Should be a 4 letter word 2. Should have no repeating characters
	 * 
	 * @param text
	 *            - the word to validate
	 * @return true if a the word is a valid guess, false otherwise.
	 */
	public static final boolean isValid(final String text) {
		if (isNCharactersLong(text, 4)) {
			if (isComprisedOnlyOfLetters(text)) {
				return isMadeOfUniqueCharacters(text) && isInDictionary(text);
			}
		}
		return false;
	}

	/**
	 * Returns the text with a link to
	 * 
	 * @param text
	 * @param url
	 * @return
	 */
	public static final String linkify(final String text, final URL url) {

		if (url == null) {
			return text;
		}

		final String urlString = "<a href='" + url + "/" + text + "'>" + text + "</a>";
		return urlString;
	}

	/**
	 * Shuffles the characters of a string.
	 * 
	 * @param input
	 * @return - the shuffled string
	 */
	public static final String shuffle(final String input) {
		final List<Character> characters = new ArrayList<Character>();
		for (final char c : input.toCharArray()) {
			characters.add(c);
		}

		final StringBuilder output = new StringBuilder(input.length());

		while (characters.size() != 0) {
			final int randPicker = (int) (Math.random() * characters.size());
			output.append(characters.remove(randPicker));
		}
		return output.toString();
	}

	/**
	 * Turn a piece of string to randomly camel-cased (sTrInG or sTRinG)
	 * 
	 * @param s
	 * @return
	 */
	public static final String wEiRDiFy(final String s) {

		if (s != null) {
			final StringBuffer camelCased = new StringBuffer();
			for (final char c : s.toCharArray()) {
				final Random r = new Random();
				final boolean caps = r.nextInt() % 2 == 0;
				final String valueOf = String.valueOf(c);
				camelCased.append(caps ? valueOf.toUpperCase(Locale.US) : valueOf.toLowerCase(Locale.US));
			}
			return camelCased.toString();
		}
		return "";
	}

	/**
	 * Returns the count of ch in string.
	 * 
	 * @param string
	 * @param ch
	 * @return
	 */
	public static final int count(final String string, final char ch) {
		int count = 0;
		if (!TextUtils.isEmpty(string)) {
			for (final char c : string.toCharArray()) {
				if (c == ch) {
					count++;
				}
			}
		}
		return count;
	}

	/**
	 * Returns a string containing characters from the set delimited by spaces.
	 * 
	 * @param chars
	 * @return
	 */
	public static final String getStringFromSet(final Set<Character> chars) {
		final StringBuffer sb = new StringBuffer();
		for (final Character ch : chars) {
			sb.append(ch);
			sb.append(' ');
		}
		return sb.toString();
	}

	/**
	 * Private constructor.
	 */
	private GuessWhatUtil() {
	}
}
